#include "Avalon/UidService/UidServer.hpp"
#include "Avalon/Base/Algorithm.hpp"
#include "Avalon/IO/Channel.hpp"
#include "Avalon/IO/ChannelAdapterServerConnection.hpp"
#include "Avalon/IO/ConnectException.hpp"
#include "Avalon/IO/ServerConnection.hpp"
#include "Avalon/ServiceLocator/ServiceLocatorClient.hpp"
#include "Avalon/Threading/AsyncCall.hpp"
#include "Avalon/Threading/LockRelease.hpp"
#include "Avalon/UidService/UidClientChannel.hpp"
#include "Avalon/UidService/UidDataStore.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Serialization;
using namespace Avalon::ServiceLocator;
using namespace Avalon::Services;
using namespace Avalon::Threading;
using namespace Avalon::UidService;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::signals2;
using namespace std;

UidServer::UidServer(ServiceLocatorClient* serviceClient,
    UidDataStore* dataStore, ServerConnection* connection,
    DataShuttleFactory* dataShuttleFactory, time_duration timeout)
    : m_serviceClient(serviceClient),
      m_dataStore(dataStore),
      m_connection(new ChannelAdapterServerConnection(connection,
        new WrapperChannelFactory(WrapperChannelFactory::ChannelCreator(
        &WrapperChannelFactory::WrapChannel<UidClientChannel>)))),
      m_service(dataShuttleFactory, timeout),
      m_isRunning(false),
      m_isShuttingDown(false) {
  SetHandler<ReserveUidsService>(m_service, m_signalHandler, m_mutex,
    bind(&UidServer::OnReserveUids, this, _1, _2));
}

UidServer::~UidServer() {
  Stop();
}

bool UidServer::IsRunning() {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  return m_isRunning;
}

void UidServer::Start() {
  boost::unique_lock<boost::mutex> lock(m_mutex);
  if(m_isRunning) {
    return;
  }
  m_connection->Start();
  m_signalHandler.Start();
  bool isOpen;
  connection closedConnection = m_connection->ConnectClosedSignal(
    m_signalHandler.GetSlot<Connection::ClosedSignal>(bind(
    &UidServer::OnConnectionClosed, this)), &isOpen);
  m_connectionState.Initialize(isOpen, closedConnection);
  if(!m_connectionState.IsOpen()) {
    m_signalHandler.Stop();
    BOOST_THROW_EXCEPTION(ConnectException());
  }
  m_isRunning = true;
  closedConnection = m_serviceClient->ConnectClosedSignal(
    m_signalHandler.GetSlot<Connection::ClosedSignal>(
    bind(&UidServer::OnServiceClientClosed, this)), &isOpen);
  m_serviceClientState.Initialize(isOpen, closedConnection);
  if(!m_serviceClientState.IsOpen()) {
    Shutdown();
    if(m_isRunning) {
      m_closedCondition.wait(lock);
    }
    m_signalHandler.Stop();
    BOOST_THROW_EXCEPTION(ConnectException("Service locator not connected."));
  }
  closedConnection = m_dataStore->ConnectClosedSignal(
    m_signalHandler.GetSlot<Connection::ClosedSignal>(
    bind(&UidServer::OnDataStoreClosed, this)));
  try {
    m_dataStore->Connect();
  } catch(std::exception&) {
    Shutdown();
    if(m_isRunning) {
      m_closedCondition.wait(lock);
    }
    m_signalHandler.Stop();
    BOOST_RETHROW;
  }
  m_dataStoreState.Initialize(true, closedConnection);
  try {
    Async<Channel*>::Ptr acceptResult = m_connection->Accept();
    acceptResult->ConnectFinishedSignal(
      m_signalHandler.GetSlot<BaseAsync::FinishedSignal>(
      bind(&UidServer::OnConnectionAccepted, this, acceptResult)));
  } catch(NotConnectedException&) {}
}

void UidServer::Stop() {
  {
    boost::unique_lock<boost::mutex> lock(m_mutex);
    if(!m_isRunning) {
      return;
    }
    Shutdown();
    if(m_isRunning) {
      m_closedCondition.wait(lock);
    }
  }
  m_signalHandler.Stop();
}

void UidServer::Shutdown() {
  m_isShuttingDown = true;
  if(m_connectionState.IsOpen()) {
    m_connection->Close();
    return;
  } else if(!m_clients.empty()) {
    for(vector<UidClientChannel*>::const_iterator i = m_clients.begin();
        i != m_clients.end(); ++i) {
      (*i)->GetConnection().Close();
    }
    return;
  } else if(m_dataStoreState.IsOpen()) {
    m_dataStore->Close();
    return;
  }
  m_serviceClientState.SetClosed();
  m_isShuttingDown = false;
  m_isRunning = false;
  m_closedCondition.notify_one();
}

void UidServer::OnConnectionAccepted(const Async<Channel*>::Ptr& result) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  if(result->GetState() == BaseAsync::EXCEPTION) {
    return;
  }
  try {
    Async<Channel*>::Ptr nextResult = m_connection->Accept();
    nextResult->ConnectFinishedSignal(
      m_signalHandler.GetSlot<BaseAsync::FinishedSignal>(
      bind(&UidServer::OnConnectionAccepted, this, nextResult)));
  } catch(NotConnectedException&) {}
  UidClientChannel* channel = static_cast<UidClientChannel*>(result->Get());
  bool isOpen;
  connection closedConnection = m_service.AddChannel(channel,
    m_signalHandler.GetSlot<Connection::ClosedSignal>(bind(
    &UidServer::OnClientClosed, this, channel)), &isOpen);
  channel->GetConnectionState().Initialize(isOpen, closedConnection);
  if(!channel->GetConnectionState().IsOpen()) {
    m_service.RemoveChannel(channel);
    delete channel;
    return;
  }
  m_clients.push_back(channel);
  AddPendingOperation(channel, m_signalHandler, m_mutex,
    bind(&UidServer::AuthenticateSession<boost::mutex>,
    static_cast<ServerMixin<UidClientChannel>*>(this), ref(m_mutex), channel,
    ref(m_service), m_serviceClient));
}

void UidServer::OnServiceClientClosed() {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  m_serviceClientState.SetClosed();
  Shutdown();
}

void UidServer::OnDataStoreClosed() {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  m_dataStoreState.SetClosed();
  Shutdown();
}

void UidServer::OnConnectionClosed() {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  m_connectionState.SetClosed();
  Shutdown();
}

void UidServer::OnClientClosed(UidClientChannel* channel) {
  boost::lock_guard<boost::mutex> lock(m_mutex);
  channel->GetConnectionState().SetClosed();
  if(channel->GetTaskQueue().IsEmpty()) {
    RemoveClient(channel);
  }
}

void UidServer::OnReserveUids(const RequestToken<ReserveUidsService>& request,
    long long blockSize) {
  UidClientChannel* channel = static_cast<UidClientChannel*>(
    request.GetChannel());
  if(!channel->IsLoggedIn()) {
    request.SendResponse(make_tuple(false, "Not logged in.", 0));
    return;
  }
  try {
    long long reserveResult = m_dataStore->Reserve(blockSize);
    request.SendResponse(make_tuple(true, "", reserveResult));
  } catch(std::exception& e) {
    request.SendResponse(make_tuple(false, e.what(), 0));
  }
}

void UidServer::RemoveClient(UidClientChannel* channel) {
  m_service.RemoveChannel(channel);
  Remove(m_clients, channel);
  delete channel;
  if(m_isShuttingDown && m_clients.empty()) {
    Shutdown();
  }
}
